package com.base.commons.tools.utils;


import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;
import net.sf.cglib.core.ClassEmitter;
import net.sf.cglib.core.Constants;
import net.sf.cglib.core.EmitUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Type;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 动态生成一个bean 并添加自己想要的成员变量
 * @author zt17861  任森平
 */
public class ReflectUtil {

 
    public static Object getTarget(Object dest, Map<String, Object> addProperties) {
        // get property map
        PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
        PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest);
        Map<String, Class> propertyMap = new LinkedHashMap<>();
        for (PropertyDescriptor d : descriptors) {
            if (!"class".equalsIgnoreCase(d.getName())) {
                propertyMap.put(d.getName(), d.getPropertyType());
            }
        }
        // add extra properties
        addProperties.forEach((k, v) -> propertyMap.put(k, v== null? Object.class:v.getClass()));
        // new dynamic bean
        DynamicBean dynamicBean = new DynamicBean(dest.getClass(), propertyMap);
        // add old value
        propertyMap.forEach((k, v) -> {
            try {
                // filter extra properties
                if (!addProperties.containsKey(k)) {
                    dynamicBean.setValue(k, propertyUtilsBean.getNestedProperty(dest, k));
                }
            } catch (Exception e) {
               e.printStackTrace();
            }
        });
        // add extra value
        addProperties.forEach((k, v) -> {
            try {
                dynamicBean.setValue(k, v);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        Object target = dynamicBean.getTarget();
        return target;
    }
 
 
    public static class DynamicBean {
        /**
         * 目标对象
         */
        private Object target;
 
        /**
         * 属性集合
         */
        private BeanMap beanMap;
 
        public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
            this.target = generateBean(superclass, propertyMap);
            this.beanMap = BeanMap.create(this.target);
        }
 
 
        /**
         * bean 添加属性和值
         *
         * @param property
         * @param value
         */
        public void setValue(String property, Object value) {
            beanMap.put(property, value);
        }
 
        /**
         * 获取属性值
         *
         * @param property
         * @return
         */
        public Object getValue(String property) {
            return beanMap.get(property);
        }
 
        /**
         * 获取对象
         *
         * @return
         */
        public Object getTarget() {
            return this.target;
        }
 
 
        /**
         * 根据属性生成对象
         *
         * @param superclass
         * @param propertyMap
         * @return
         */
        private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
            MyBeanGenerator generator = new MyBeanGenerator();
            if (null != superclass) {
                generator.setSuperclass(superclass);
            }
            BeanGenerator.addProperties(generator, propertyMap);
            return generator.create();
        }
    }

   static class MyBeanGenerator extends BeanGenerator {
       private Map pro = new LinkedHashMap();
        public MyBeanGenerator() {
        }
        @Override
       public void addProperty(String name, Class type) {
            super.addProperty(name,type);
           if (this.pro.containsKey(name)) {
               throw new IllegalArgumentException("Duplicate property name \"" + name + "\"");
           } else {
               this.pro.put(name, Type.getType(type));
           }
       }
        private Object getFieldValue(String fieldName) throws Exception{
            Field field = this.getClass().getSuperclass().getDeclaredField(fieldName);
            field.setAccessible(true);
            Object o = field.get(this);
            return o;
        }
        @Override
        public void generateClass(ClassVisitor v) throws Exception {
            Map props = (Map) getFieldValue("props");
            Class superclass = (Class) getFieldValue("superclass");
            int size = props.size();
            String[] names = (String[])((String[])pro.keySet().toArray(new String[size]));
            Type[] types = new Type[size];

            for(int i = 0; i < size; ++i) {
                types[i] = (Type)pro.get(names[i]);
            }

            ClassEmitter ce = new ClassEmitter(v);
            ce.begin_class(46, 1, this.getClassName(), superclass != null ? Type.getType(superclass) : Constants.TYPE_OBJECT, (Type[])null, (String)null);
            EmitUtils.null_constructor(ce);
            for(int i = 0; i < names.length; ++i) {
                String fieldName =names[i];
                ce.declare_field(2, fieldName, types[i], (Object)null);
                EmitUtils.add_property(ce, names[i], types[i], fieldName);
            }
            ce.end_class();
        }
    }

}